﻿using System;

using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Drawing;
using Silvermoon.OpenGL.Native;

namespace Silvermoon.OpenGL
{
    public class Device : IDisposable
    {
        public const int CLV = 1 << 16;

        #region ctor/dtor

        private Device()
            : base()
        {
            this.ClientState = new ClientState();
            this.Capabilities = new Capabilities();
        }

        public Device(System.Windows.Forms.Control window)
            : this()
        {
            this.control = window;
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(OnUnhandledException);
            Initialize(window);
            window.Resize += new EventHandler(OnControlResize);

        }

        void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            Cleanup();
        }

        public void Dispose()
        {
            Cleanup();
            AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException;
        }


        #endregion
        #region Fields

        private static Device instance;
        private IntPtr display;
        private Surface surface;
        private IntPtr context;
        private Surface renderTarget;
        private Rectangle viewPort;
        private System.Windows.Forms.Control control;

        #endregion
        #region Properties

        public ClientState ClientState { get; private set; }
        public Capabilities Capabilities { get; private set; }

        public static Device Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new Device();
                }
                return instance;
            }
        }


        public ShadeModel ShadeModel
        {
            set { gl.ShadeModel(value); }
        }

        public Comparison DepthFunction
        {
            set { gl.DepthFunc(value); }
        }

        public MatrixMode MatrixMode
        {
            set
            {
                gl.MatrixMode(value);
            }
        }

        public TextureUnit ActiveTexture
        {
            set
            {
                gl.ActiveTexture(value);
            }
        }

        public TextureUnit ClientActiveTexture
        {
            set
            {
                gl.ClientActiveTexture(value);
            }
        }

        public Rectangle ViewPort
        {
            get { return viewPort; }
            set
            {
                viewPort = value;
                gl.Viewport(value.Left, value.Top, value.Width, value.Height);
            }
        }


        public IntPtr Display { get { return display; } }

        public Surface Surface { get { return surface; } }


        #endregion
        #region private Methods


        private void Initialize(System.Windows.Forms.Control window)
        {
            display = egl.GetDisplay(new EGLNativeDisplayType(window));
            //display = egl.GetDisplay(new EGLNativeDisplayType());
            int major, minor;
            egl.Initialize(display, out major, out minor);


            surface = CreateWindowSurface(window);
            renderTarget = surface;

            context = egl.CreateContext(display, config, IntPtr.Zero, null);
            SetRenderTarget(surface, surface);
            ViewPort = window.ClientRectangle;
            egl.SwapInterval(Display, 0);

        }

        void OnControlResize(object sender, EventArgs e)
        {
            ViewPort = control.ClientRectangle;
        }


        private void Cleanup()
        {
            if (display != IntPtr.Zero)
            {
                if (renderTarget != null) renderTarget.Dispose();
                renderTarget = null;

                if (!egl.DestroyContext(display, context)) throw new Exception("Error while destroying context.");

                if (!egl.Terminate(display)) throw new Exception("Error while terminating display.");

                display = IntPtr.Zero;
            }
        }

        #endregion
        #region Methods

        public void SetRenderTarget(Surface renderTarget, Surface read)
        {
            if (renderTarget == null) throw new ArgumentNullException("renderTarget");
            if (read == null) throw new ArgumentNullException("read");
            CheckDisposeRenderTarget(renderTarget);
            egl.MakeCurrent(display, renderTarget.SurfacePtr, read.SurfacePtr, context);
        }

        private void CheckDisposeRenderTarget(Surface renderTarget)
        {
            if (this.renderTarget != renderTarget)
            {
                this.renderTarget.Dispose();
                renderTarget = null;
            }
        }

        int config;
        public IntPtr surfacePtr;

        public Surface CreateWindowSurface(System.Windows.Forms.Control window)
        {
            int[] configs = new int[10];
            int[] attribList = new int[] 
            {         
                egl.EGL_RED_SIZE, 5, 
                egl.EGL_GREEN_SIZE, 6, 
                egl.EGL_BLUE_SIZE, 5, 
                egl.EGL_DEPTH_SIZE, 16 , 
                egl.EGL_ALPHA_SIZE,0,               
                egl.EGL_SURFACE_TYPE, egl.EGL_WINDOW_BIT,
                egl.EGL_STENCIL_SIZE, 0,
                //egl.EGL_NATIVE_RENDERABLE,egl.EGL_TRUE,
                //egl.EGL_RENDERABLE_TYPE,egl.EGL_OPENGL_ES2_BIT,
                egl.EGL_NONE, egl.EGL_NONE 
            };

            int numConfig;

            if (!egl.ChooseConfig(display, attribList, configs, configs.Length, out numConfig) || numConfig < 1)
                throw new NotSupportedException("Unable to choose config.");

            config = configs[0];

            IntPtr eglSurface = egl.CreateWindowSurface(display, config, window.Handle, null);
            this.surfacePtr = eglSurface;
            if (eglSurface == IntPtr.Zero) throw new NotSupportedException("Could not create surface.");

            Surface surface = new Surface(this, eglSurface) { Width = window.Width, Height = window.Height };
            return surface;
        }


        unsafe public Surface CreatePBuffer(GlesPixelFormat format)
        {
            format = GlesPixelFormat.RGBA;
            int red, green, blue, alpha;
            switch (format)
            {
                case GlesPixelFormat.RGB: { red = 5; green = 6; blue = 5; alpha = 0; } break;
                case GlesPixelFormat.RGBA: { red = 4; green = 4; blue = 4; alpha = 4; } break;
                default: throw new ArgumentOutOfRangeException("format");
            }

            int[] configs = new int[10];
            int[] attribList = new int[] 
            { 
                // see http://java.sun.com/javame/reference/apis/jsr239/javax/microedition/khronos/egl/EGL10.html
                // for a desciption of valid config values and attributes for eglChooseConfig and eglCreatePBufferSurface.
                egl.EGL_RED_SIZE, red, 
                egl.EGL_GREEN_SIZE, green, 
                egl.EGL_BLUE_SIZE, blue,
                egl.EGL_ALPHA_SIZE,alpha,
                //egl.EGL_NATIVE_RENDERABLE,egl.EGL_TRUE, // not supported for X1!
                //egl.EGL_DEPTH_SIZE, 16 , 
                egl.EGL_STENCIL_SIZE, egl.EGL_DONT_CARE,
                egl.EGL_SURFACE_TYPE, egl.EGL_PBUFFER_BIT,
                //egl.EGL_BIND_TO_TEXTURE_RGB,egl.EGL_TRUE,  // => X1 doesn't support that, since it's version ist only 1.0 and not 1.1!
                egl.EGL_NONE, egl.EGL_NONE 
            };



            int numConfig;

            //egl.GetConfigs(display, configs, configs.Length, out numConfig);

            if (!egl.ChooseConfig(display, attribList, configs, configs.Length, out numConfig) || numConfig < 1)
                throw new NotSupportedException("Unable to choose config.");

            int[] attr = new int[]
            {    
                egl.EGL_WIDTH,32,
                egl.EGL_HEIGHT,32,
                //egl.EGL_TRANSPARENT_TYPE,egl.EGL_TRANSPARENT_RGB, // OR EGL_NONE
                //egl.EGL_TEXTURE_FORMAT,egl.EGL_TEXTURE_RGB,
                //egl.EGL_TEXTURE_TARGET,egl.EGL_TEXTURE_2D,
                //egl.EGL_MIPMAP_TEXTURE,egl.EGL_TRUE,
                //egl.EGL_SURFACE_TYPE,egl.EGL_PBUFFER_BIT,
                egl.EGL_NONE,egl.EGL_NONE,
                };

            int config = 0;

            IntPtr sf = egl.CreatePbufferSurface(display, config, attr);
            if (sf != IntPtr.Zero)
            {
                Surface result = new Surface(this, sf);
                return result;
            }

            throw new NotSupportedException("Could not create surface.");
        }

        public void LoadIdentity()
        {
            gl.LoadIdentity();
        }

        public void Present()
        {
            egl.SwapBuffers(display, renderTarget.SurfacePtr);
        }

        public void Clear(ColorBufferBit mask)
        {
            gl.Clear(mask);
        }

        public void ClearColor(float red, float green, float blue, float alpha)
        {
            gl.ClearColor(red, green, blue, alpha);
        }

        public void ClearColor(System.Drawing.Color color)
        {
            float div = 255F;
            ClearColor((float)color.R / div, (float)color.G / div, (float)color.B / div, (float)color.A / div);
        }

        public void SetMatrixMode(MatrixMode matrixMode)
        {
            gl.MatrixMode(matrixMode);
        }

        public void Translate(float x, float y, float z)
        {
            gl.Translatef(x, y, z);
        }

        public void Translate(int x, int y, int z)
        {
            gl.Translatex(x, y, z);
        }

        public void Rotate(float angle, float x, float y, float z)
        {
            gl.Rotatef(angle, x, y, z);
        }

        public void Rotate(int angle, int x, int y, int z)
        {
            gl.Rotatex(angle, x, y, z);
        }

        public void Scale(int x, int y, int z)
        {
            gl.Scalex(x, y, z);
        }

        public void Scale(float x, float y, float z)
        {
            gl.Scalef(x, y, z);
        }

        public void Perspective(float fovy, float aspect, float zNear, float zFar)
        {
            if (zNear <= 0F) throw new ArgumentOutOfRangeException("zNear must be positive.");
            if (zFar <= 0F) throw new ArgumentOutOfRangeException("zFar must be positive.");
            float xmin, xmax, ymin, ymax;

            ymax = zNear * (float)Math.Tan(fovy * Math.PI / 360.0F);
            ymin = -ymax;
            xmin = ymin * aspect;
            xmax = ymax * aspect;

            gl.Frustumf(xmin, xmax, ymax, ymin, zNear, zFar);
        }

        public void Orthof(float left, float right, float bottom, float top, float znear, float zfar)
        {
            gl.Orthof(left, right, bottom, top, znear, zfar);
        }

        public void VertexPointer(int stride, int[] array)
        {
            gl.VertexPointer(3, glValueType.Fixed, stride, array);
        }

        public void VertexPointer(int stride, float[] array)
        {
            gl.VertexPointer(3, glValueType.Float, stride, array);
        }


        public void Flush()
        {
            gl.Flush();
        }

        public void SetBlendFunction(BlendFactor sourceFactor, BlendFactor destFactor)
        {
            gl.BlendFunc(sourceFactor, destFactor);
        }

        public void SetClearDepthf(float depth)
        {
            gl.ClearDepthf(depth);
        }

        public void SetAlphaFunction(Comparison comparison, BlendFactor blendFactor)
        {
            gl.AlphaFunc(comparison, blendFactor);
        }

        public void SetHint(HintTarget hintTarget, HintMode hintMode)
        {
            gl.Hint(hintTarget, hintMode);
        }

        public void SetColor(float red, float green, float blue, float alpha)
        {
            gl.Color4f(red, green, blue, alpha);
        }

        public void SetColor(Color color)
        {
            float red = (float)color.R / 255f;
            float green = (float)color.G / 255f;
            float blue = (float)color.B / 255f;
            float alpha = (float)color.A / 255f;

            gl.Color4f(red, green, blue, alpha);
            //int clv = gl.CLV;
            //int red = clv * color.R;
            //int green = clv * color.G;
            //int blue = clv * color.B;
            //int alpha = clv * color.A;
            //gl.Color4x(red, green, blue, alpha);
        }

        #endregion





        public void SetColorMask(bool red, bool green, bool blue, bool alpha)
        {
            gl.ColorMask(red ? (byte)1 : (byte)0, green ? (byte)1 : (byte)0, blue ? (byte)1 : (byte)0, alpha ? (byte)1 : (byte)0);
        }

        public Surface CreateImageSurface(Bitmap bm, GlesPixelFormat format)
        {
            int red, green, blue, alpha;
            switch (format)
            {
                case GlesPixelFormat.RGB: { red = 5; green = 6; blue = 5; alpha = 0; } break;
                case GlesPixelFormat.RGBA: { red = 8; green = 8; blue = 8; alpha = 8; } break;
                default: throw new ArgumentOutOfRangeException("format");
            }

            int[] configs = new int[10];
            int[] attribList = new int[] 
            { 
                egl.EGL_RED_SIZE, red, 
                egl.EGL_GREEN_SIZE, green,
                egl.EGL_BLUE_SIZE, blue,
                egl.EGL_ALPHA_SIZE, alpha,
                egl.EGL_SURFACE_TYPE, egl.EGL_PIXMAP_BIT,
                egl.EGL_NONE, egl.EGL_NONE 
            };


            int numConfig;
            if (!egl.ChooseConfig(display, attribList, configs, configs.Length, out numConfig) || numConfig < 1)
                throw new NotSupportedException("Unable to choose config.");


            int[] attr = new int[]
            {
                    egl.EGL_NONE,egl.EGL_NONE,
                };

            IntPtr hBitmap = bm.GetHbitmap();
            int config = configs[0];
            IntPtr sf = egl.CreatePixmapSurface(display, config, hBitmap, attr);
            if (sf != IntPtr.Zero)
            {
                Surface result = new Surface(this, sf);
                return result;
            }

            throw new NotSupportedException("Could not create surface.");
        }
    }

}
